//
// Created by mmuee on 2023/2/1.
//

#include "Loger.hpp"

#ifdef ANDROID
#include <android/log.h>
#elif ESP_PLATFORM
#include "esp_log.h"
#endif

#include <utility>
#include <cstdarg>
#include <unordered_map>
#include <iostream>
#include <algorithm>
#include <vector>

Loger::Loger()
{
#if USE_STD_LOG_OUT == 1 || CONFIG_ESP_USE_STD_LOG_OUT
    this->addStore(std::make_shared<LogStoreStd>());
#endif
}

Loger::~Loger() = default;

Loger* Loger::shared()
{
    static Loger _ins;
    return &_ins;
}

bool Loger::hasLogFlag(LogFlag flag)
{
    return (int32_t(level.load(std::memory_order_relaxed)) & int32_t(flag)) > 0;
}

void Loger::setLogLevel(LogLevel level_)
{
    level.store(level, std::memory_order_release);
}

void Loger::addRecord(const LogRecord& record)
{
    for (const auto& store : stores)
    {
        store->put(record);
    }
}

bool Loger::operator<<(const LogRecord&& record)
{
    Loger::shared()->addRecord(record);
    return true;
}

void Loger::addStore(const LogStoreRef& store)
{
    stores.push_back(store);
}

#pragma mark - LogRecord

LogRecord::LogRecord(const std::string& content, LogFlag flag, uint32_t line, std::string file, std::string fileName,
                     std::string function)
    : flag(flag), line(line), file(std::move(file)), fileName(std::move(fileName)), function(std::move(function))
{
    cstream << content;
#if FIX_FILE_NAME
    int32_t idx = this->fileName.find_last_of('/') + 1;
    if (idx >= 0 && idx < this->fileName.length())
    {
        this->fileName = this->fileName.substr(idx, this->fileName.length() - idx);
    }
#endif
}

std::string LogRecord::content() const
{
    return cstream.str();
}

const std::string& LogRecord::flag_name() const
{
    static std::unordered_map<LogFlag, std::string> flags;
    static std::once_flag oc;
    std::call_once(oc, [&]() {
        flags[LogFlagVerbose] = "Verbose";
        flags[LogFlagDebug] = "Debug";
        flags[LogFlagInfo] = "Info";
        flags[LogFlagWarning] = "Warning";
        flags[LogFlagError] = "Error";
    });
    return flags[flag];
}

#pragma mark - LogStoreStd

#ifdef ANDROID

static std::vector<std::string> splitString(const std::string& content, int length)
{
    std::vector<std::string> result;

    int strLength = content.length();
    int startIndex = 0;
    while (startIndex < strLength)
    {
        int endIndex = startIndex + length;
        std::string substr = content.substr(startIndex, length);
        result.push_back(substr);
        startIndex = endIndex;
    }
    return result;
}

void LogStoreStd::put(const LogRecord& record)
{

    android_LogPriority priority;
    switch (record.flag)
    {
    case LogFlagError:
        priority = ANDROID_LOG_ERROR;
        break;
    case LogFlagWarning:
        priority = ANDROID_LOG_WARN;
        break;
    case LogFlagInfo:
        priority = ANDROID_LOG_INFO;
        break;
    case LogFlagDebug:
        priority = ANDROID_LOG_DEBUG;
        break;
    case LogFlagVerbose:
        priority = ANDROID_LOG_VERBOSE;
        break;
    }

    // 安卓每条日志最多输出1023个字符, 超出的不显示
    auto maxLen = 768;
    if (record.content().length() > maxLen)
    {
        auto subs = splitString(record.content(), maxLen);
        __android_log_print(priority, record.fileName.c_str(), "[%s(%d) %s()][%s]:%s", record.fileName.c_str(),
                            record.line, record.function.c_str(), record.flag_name().c_str(), ">>>> start");
        for (const auto& sub : subs)
        {
            __android_log_print(priority, record.fileName.c_str(), "%s", sub.c_str());
        }
        __android_log_print(priority, record.fileName.c_str(), "[%s(%d) %s()][%s]:%s", record.fileName.c_str(),
                            record.line, record.function.c_str(), record.flag_name().c_str(), "<<<< end");
    }
    else
    {
        __android_log_print(priority, record.fileName.c_str(), "[%s(%d) %s()][%s]:%s", record.fileName.c_str(),
                            record.line, record.function.c_str(), record.flag_name().c_str(), record.content().c_str());
    }
}

#elif defined(__linux__) || defined(__EMSCRIPTEN__) || defined(_WIN32) || defined(__APPLE__)

void LogStoreStd::put(const LogRecord& record)
{
    if (record.flag == LogFlagError)
    {
        std::cerr << '[' << record.fileName << "(" << record.line << ")" << ' ' << record.function << "()]["
                  << record.flag_name() << "]:" << record.content() << '\n';
    }
    else
    {
        std::cout << '[' << record.fileName << "(" << record.line << ")" << ' ' << record.function << "()]["
                  << record.flag_name() << "]:" << record.content() << '\n';
    }
}
#elif defined(ESP_PLATFORM)
void LogStoreStd::put(const LogRecord& record)
{
    switch (record.flag)
    {
    case LogFlagError:
        ESP_LOGE(record.fileName.c_str(), "%s", record.content().c_str());
        break;
    case LogFlagWarning:
        ESP_LOGW(record.fileName.c_str(), "%s", record.content().c_str());
        break;
    case LogFlagInfo:
        ESP_LOGI(record.fileName.c_str(), "%s", record.content().c_str());
        break;
    case LogFlagDebug:
        ESP_LOGD(record.fileName.c_str(), "%s", record.content().c_str());
        break;
    case LogFlagVerbose:
        ESP_LOGV(record.fileName.c_str(), "%s", record.content().c_str());
        break;
    }
}

#endif

std::string logFormat()
{
    return {};
}

std::string logFormat(const char* fmt, ...)
{
    va_list args1;
    va_list args2;
    va_start(args1, fmt);
    va_copy(args2, args1);
    auto size = vsnprintf(nullptr, 0, fmt, args1);
    std::string str;
    str.resize(size);
    va_end(args1);
    vsnprintf(str.data(), size + 1, fmt, args2);
    va_end(args2);
    return str;
}

#pragma mark - LogStoreFile
#ifdef ANDROID

#elif defined(__linux__) || defined(__EMSCRIPTEN__)

void LogStoreFile::put(const LogRecord& record)
{
    if (!outputStream.is_open())
    {
        outputStream.open(outputFile.c_str(), ios_base::app);
    }
    outputStream << logFormat("[%s %s()][%d][%s]:%s\n", record.fileName.c_str(), record.function.c_str(), record.line,
                              record.flag_name().c_str(), record.content().c_str());
    outputStream.flush();
    outputStream.close();
}

#elif defined(_WIN32)
#endif
